package tmt.usercenter.web.configure.security.bean;

import org.apache.ignite.Ignite;
import org.apache.ignite.IgniteCache;
import org.apache.ignite.cache.CacheMode;
import org.apache.ignite.cache.CachePeekMode;
import org.apache.ignite.cache.CacheRebalanceMode;
import org.apache.ignite.configuration.CacheConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2RefreshToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.AuthenticationKeyGenerator;
import org.springframework.security.oauth2.provider.token.DefaultAuthenticationKeyGenerator;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;


import javax.cache.expiry.CreatedExpiryPolicy;
import javax.cache.expiry.Duration;
import javax.cache.expiry.TouchedExpiryPolicy;
import java.util.*;
import java.util.concurrent.TimeUnit;

public class IgniteTokenStore implements TokenStore {

    //默认访问令牌过期时间
    private static final Long ACCESS_TOKEN_DURATION = 300L;

    private static final TimeUnit TIME_UNIT = TimeUnit.SECONDS;

    private static final String TOKEN_CACHE_NAME = "oauth_token_cache";

    private static final String ACCESS = "access:";
    private static final String AUTH_TO_ACCESS = "auth_to_access:";
    private static final String AUTH = "auth:";
    private static final String REFRESH_AUTH = "refresh_auth:";
    private static final String ACCESS_TO_REFRESH = "access_to_refresh:";
    private static final String REFRESH = "refresh:";
    private static final String REFRESH_TO_ACCESS = "refresh_to_access:";
    private static final String CLIENT_ID_TO_ACCESS = "client_id_to_access:";
    private static final String UNAME_TO_ACCESS = "uname_to_access:";

    private AuthenticationKeyGenerator authenticationKeyGenerator = new DefaultAuthenticationKeyGenerator();

    //AccessToken缓存对象
    private IgniteCache accessTokenCache;

    //RefreshToken缓存对象
    private IgniteCache refreshTokenCache;

    public IgniteTokenStore(Ignite igniteInstance){
        this(igniteInstance, ACCESS_TOKEN_DURATION);
    }

    public IgniteTokenStore(Ignite igniteInstance,
                            Long accessTokenDuration){
        CacheConfiguration cfg = new CacheConfiguration<Integer, Integer>(TOKEN_CACHE_NAME);
        cfg.setCacheMode(CacheMode.PARTITIONED);
        cfg.setRebalanceMode(CacheRebalanceMode.SYNC);
        //cfg.setBackups(1);
        cfg.setEagerTtl(true);

        //刷新令牌永不过期
        refreshTokenCache = igniteInstance.getOrCreateCache(cfg);

        //设置访问令牌在缓存中的过期策略
        accessTokenCache = refreshTokenCache.withExpiryPolicy(new TouchedExpiryPolicy(new Duration(TIME_UNIT,
                accessTokenDuration != null ? accessTokenDuration : ACCESS_TOKEN_DURATION)));
    }

    private String getAccessKey(String tokenValue){
        return ACCESS + tokenValue;
    }

    private String getAuthKey(String tokenValue){
        return AUTH + tokenValue;
    }

    private String getAuthToAccess(OAuth2Authentication authentication){
        return AUTH_TO_ACCESS + authenticationKeyGenerator.extractKey(authentication);
    }

    private String getClientIdToAccess(OAuth2Authentication authentication){
        return getClientIdToAccess(authentication.getOAuth2Request().getClientId());
    }

    public String getClientIdToAccess(String clientId){
        return CLIENT_ID_TO_ACCESS + clientId;
    }

    private String getApprovalKey(OAuth2Authentication authentication) {
        String userName = authentication.getUserAuthentication() == null ? ""
                : authentication.getUserAuthentication().getName();
        return getApprovalKey(authentication.getOAuth2Request().getClientId(), userName);
    }

    public String getApprovalKey(String clientId, String userName) {
        return UNAME_TO_ACCESS + clientId + (userName == null ? "" : ":" + userName);
    }

    private static String getRefreshToAccessKey(OAuth2RefreshToken token){
        return REFRESH_TO_ACCESS + token.getValue();
    }

    private static String getAccessToRefreshKey(OAuth2AccessToken token){
        return ACCESS_TO_REFRESH + token.getValue();
    }

    private static String getRefreshKey(String refreshTokenValue){
        return REFRESH + refreshTokenValue;
    }

    private static String getRefreshAuthKey(String refreshTokenValue){
        return REFRESH_AUTH + refreshTokenValue;
    }

    private static void appendToList(IgniteCache cache, String key, OAuth2AccessToken obj){
        List<OAuth2AccessToken> list =  null;
        if (cache.containsKey(key))
            list = (List)cache.get(key);
        else {
            list = new ArrayList<>();
        }
        list.add(obj);
        cache.put(key, list);
    }

    private static void removeFromList(IgniteCache cache, String key, OAuth2AccessToken obj){
        List<OAuth2AccessToken> list = (List<OAuth2AccessToken>)cache.get(key);
        Optional optional = list.stream().filter(x-> Objects.equals(x.getValue(), obj.getValue())).findFirst();
        if (optional.isPresent()){
            OAuth2AccessToken token = (OAuth2AccessToken)optional.get();
            list.remove(token);
        }
    }

    @Override
    public OAuth2Authentication readAuthentication(OAuth2AccessToken token) {
        return readAuthentication(token.getValue());
    }

    @Override
    public OAuth2Authentication readAuthentication(String token) {
        return (OAuth2Authentication) accessTokenCache.get(getAuthKey(token));
    }

    @Override
    public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
        //缓存：访问令牌值——访问令牌对象
        accessTokenCache.put(getAccessKey(token.getValue()), token);
        //缓存：访问令牌值——验证信息对象
        accessTokenCache.put(getAuthKey(token.getValue()), authentication);
        //缓存：验证信息——访问令牌对象
        accessTokenCache.put(getAuthToAccess(authentication), token);
        //缓存：客户端ID——访问令牌对象
        appendToList(accessTokenCache, getClientIdToAccess(authentication), token);

        if(!authentication.isClientOnly()){
            appendToList(accessTokenCache, getApprovalKey(authentication), token);
        }

        OAuth2RefreshToken refreshToken = token.getRefreshToken();
        if (refreshToken != null && refreshToken.getValue() != null) {
            refreshTokenCache.put(getRefreshToAccessKey(token.getRefreshToken()), token);
            refreshTokenCache.put(getAccessToRefreshKey(token), token.getRefreshToken());
        }
    }

    @Override
    public OAuth2AccessToken readAccessToken(String tokenValue) {
        System.out.println("---------------readAccessToken----------------");
        return (OAuth2AccessToken) accessTokenCache.get(getAccessKey(tokenValue));
    }

    @Override
    public void removeAccessToken(OAuth2AccessToken token) {
        OAuth2Authentication authentication = (OAuth2Authentication)accessTokenCache.get(getAuthKey(token.getValue()));

        accessTokenCache.remove(getAccessKey(token.getValue()));
        accessTokenCache.remove(getAuthKey(token.getValue()));
        accessTokenCache.remove(getAuthToAccess(authentication));
        removeFromList(accessTokenCache, getClientIdToAccess(authentication), token);

        if(!authentication.isClientOnly()){
            removeFromList(accessTokenCache, getApprovalKey(authentication), token);
        }

        OAuth2RefreshToken refreshToken = token.getRefreshToken();
        if (refreshToken != null && refreshToken.getValue() != null) {
            accessTokenCache.remove(getRefreshToAccessKey(token.getRefreshToken()));
            accessTokenCache.remove(getAccessToRefreshKey(token));
        }
    }

    @Override
    public void storeRefreshToken(OAuth2RefreshToken refreshToken, OAuth2Authentication authentication) {
        refreshTokenCache.put(getRefreshKey(refreshToken.getValue()), refreshToken);
        refreshTokenCache.put(getRefreshAuthKey(refreshToken.getValue()), authentication);
    }

    @Override
    public OAuth2RefreshToken readRefreshToken(String tokenValue) {
        System.out.println("---------------storeAccessToken----------------");
        return (OAuth2RefreshToken) refreshTokenCache.get(getRefreshKey(tokenValue));
    }

    @Override
    public OAuth2Authentication readAuthenticationForRefreshToken(OAuth2RefreshToken token) {
        return (OAuth2Authentication) refreshTokenCache.get(getRefreshAuthKey(token.getValue()));
    }

    @Override
    public void removeRefreshToken(OAuth2RefreshToken token) {
        refreshTokenCache.remove(getRefreshKey(token.getValue()));
        refreshTokenCache.remove(getRefreshAuthKey(token.getValue()));

        OAuth2AccessToken accessToken = (OAuth2AccessToken) refreshTokenCache.get(getRefreshToAccessKey(token));
        refreshTokenCache.remove(getRefreshToAccessKey(token));
        refreshTokenCache.remove(getAccessToRefreshKey(accessToken));
    }

    @Override
    public void removeAccessTokenUsingRefreshToken(OAuth2RefreshToken refreshToken) {
        OAuth2AccessToken accessToken = (OAuth2AccessToken) accessTokenCache.get(getRefreshToAccessKey(refreshToken));
        if (accessToken != null)
            removeAccessToken(accessToken);
    }

    @Override
    public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) {
        OAuth2AccessToken accessToken = (OAuth2AccessToken)accessTokenCache.get(getAuthToAccess(authentication));
        if (accessToken != null) {
            OAuth2Authentication storedAuthentication = readAuthentication(accessToken.getValue());
            String key = authenticationKeyGenerator.extractKey(authentication);
            if ((storedAuthentication == null || !key.equals(authenticationKeyGenerator.extractKey(storedAuthentication)))) {
                // Keep the stores consistent (maybe the same user is
                // represented by this authentication but the details have
                // changed)
                storeAccessToken(accessToken, authentication);
            }
        }
        return accessToken;
    }

    @Override
    public Collection<OAuth2AccessToken> findTokensByClientIdAndUserName(String clientId, String userName) {
        List<OAuth2AccessToken> list= (List<OAuth2AccessToken>)accessTokenCache.get(getApprovalKey(clientId, userName));
        return list != null ? list : new ArrayList<>();
    }

    @Override
    public Collection<OAuth2AccessToken> findTokensByClientId(String clientId) {
        List<OAuth2AccessToken> list= (List<OAuth2AccessToken>)accessTokenCache.get(getClientIdToAccess(clientId));
        return list != null ? list : new ArrayList<>();
    }

}
